%%%-------------------------------------------------------------------
%%% @author Viktor Green <viktor@student.gu.se>
%%% @copyright (C) 2011, Viktor Green
%%% @doc
%%% Handles connection with tracker
%%%
%%% @todo Probably needs a rewrite or two.
%%% @end
%%% Created :  9 Nov 2011 by Viktor Green <viktor@student.gu.se>
%%%-------------------------------------------------------------------
-module(tracker).

-export([connect/8,parse_peers/1]).

-include_lib("kernel/include/inet.hrl").

-type address() :: {IPAddress :: inet:ip_address(), Port :: integer()}.

-ifdef(debug).
-define(LOG(X), io:format("[~p:~p:~p] - {~p,~p}: ~p~n", tuple_to_list(time()) ++ [?MODULE, ?LINE, X])).
-else.
-define(LOG(X), true).
-endif.

%%--------------------------------------------------------------------
%% @doc
%% Connect to a tracker with the specified information, and return
%% a list of peers provided by the tracker.
%% @end
%%--------------------------------------------------------------------
-spec connect(Address :: string(), Info_hash :: list(),
	      PeerId :: string(), Port :: integer(),
	      Uploaded :: integer(), Downloaded :: integer(),
	      Left :: integer(), Event :: string()) ->
		     {ok, bencode:b_term()} |
		     {error, Reason :: term()}.
connect(Address, Info_hash, PeerId, Port,
	Uploaded, Downloaded, Left, Event) ->
    URLed_Hash = convert_byte_to_hex(Info_hash, []),
    inets:start(),
    case Address of
	[$h,$t,$t,$p,$s | _] -> ssl:start();
	_ -> ok
    end,
    First_delimiter = case lists:member($?, Address) of
			  true ->
			      $&;
			  false ->
			      $?
		      end,
    ?LOG(Address),
    case httpc:request(get,
		       {Address ++
			    [First_delimiter | "info_hash="] ++
			    URLed_Hash ++
			    "&peer_id=" ++ PeerId ++ 
			    "&port=" ++ integer_to_list(Port) ++
			    "&uploaded=" ++
			    integer_to_list(Uploaded) ++
			    "&downloaded=" ++
			    integer_to_list(Downloaded) ++ 
			    "&left=" ++ integer_to_list(Left) ++ 
			    "&event=" ++ Event,
		       []},
		       [{timeout, 2000}], []) of
	{ok, {{_Version, 200, _Ok}, _Headers, Response}} ->
	    ?LOG(Response),
	    try bencode:decode(Response) of
		Data -> {ok, Data}
	    catch
		error:Reason ->
		    ?LOG(Reason),
		    {error, Reason}
	    end;
	{ok, {{_Version, _Not200, Reason}, _Headers, _Response}} ->
	    ?LOG("Tracker connection failed - " ++
		    integer_to_list(_Not200) ++ Reason),
	    {error, Reason};
	{error, Error} ->
	    ?LOG(Error),
	    {error, Error}
    end.

%%--------------------------------------------------------------------
%% @doc
%% Select what parser to use to parse the list of peers.
%%
%% The peers can be in either dictionary or binary format, this
%% function selects what parser to use for the provided peer list.
%%
%% If this function receives the whole tracker response (in a
%% dictionary) it will extract the peers value and recurse.
%% @end
%%--------------------------------------------------------------------
-spec parse_peers({string | list, List :: list()}) -> [address()].
parse_peers({string, List}) ->
    parse_binary_peers(list_to_binary(List));
parse_peers({list, List}) ->
    parse_dictionary_peers(List);
parse_peers({dictionary, Dictionary}) ->
    parse_peers(torrent_file:get_value("peers", Dictionary)).

%%--------------------------------------------------------------------
%% @doc
%% Parse a binary peer list.
%%
%% The first four bytes in the binary is the four octets in a IPv4
%% address, the following two bytes is the port (0-65535)
%% @end
%%--------------------------------------------------------------------
-spec parse_binary_peers(binary()) -> [address()].
parse_binary_peers(<<Oct1, Oct2, Oct3, Oct4, Port:16, Rem/binary>>) ->   
    %[{integer_to_list(Oct1) ++ "." ++ integer_to_list(Oct2) ++ "." ++
    %  integer_to_list(Oct3) ++ "." ++ integer_to_list(Oct4), Port} |
    % parse_binary_peers(Rem)];
    [{{Oct1, Oct2, Oct3, Oct4}, Port} | parse_binary_peers(Rem)];
parse_binary_peers(<<>>) -> 
    [].

%%--------------------------------------------------------------------
%% @doc
%% Parse a dictionary peer list
%%
%% This peer list consists of bencoded dictionaries with keys for ip 
%% and port.
%% The list should be parsed from the original bencoded string to
%% the erlang representation of it before used in this function.
%% @end
%%--------------------------------------------------------------------
-spec parse_dictionary_peers(PeerList :: [bencode:b_dictionary()]) ->
				    [address()].
parse_dictionary_peers([Head | Tail]) ->
    {string, Addr} = torrent_file:get_value("ip", Head), 
    {ok, #hostent{h_addr_list = [IP | _]}} = inet:gethostbyaddr(Addr),
    {integer, Port} = torrent_file:get_value("port", Head),
    [{IP, Port} | parse_dictionary_peers(Tail)];
parse_dictionary_peers([]) ->
    [].

%%--------------------------------------------------------------------
%% @doc
%% Convert a bytestring to a hexstring (used for URLEncoding of binary
%% data)
%% @end
%%--------------------------------------------------------------------
-spec convert_byte_to_hex(List :: iolist(), Acc :: string()) -> 
				 HexString :: string().
convert_byte_to_hex([Head | Tail], Acc) ->
    convert_byte_to_hex(Tail, Acc ++ "%" ++
			    io_lib:format("~2.16.0b", [Head]));
convert_byte_to_hex([], Acc) ->
    lists:flatten(Acc).
